String and StringBuilder in C#
So we all use the Strings in C# and most of us consider ourselves experts. But are we really experts. When you’re getting tested and you don’t have Visual Studio or MSDN in front of you, are you ready to answer the questions with confidence?
Sometimes it is worth while to re-learn the basics because often simple features you are not using would save you time and prevent you from failing to appear as knowledgeable as you really are. Especially if you have not had a project where you manipulate strings, because you do other complex tasks and you do little more with strings than assign them values and compare them.
Well, here are the two links to the String and StringBuilder objects on MSDN.
Take a moment and read the MSDN pages about these objects even if you have read them before.
Here are some questions that you should answer about these classes.
- Which is mutable and which is immutable? What does that mean?
Answer:
String is immutable. That means that once you create a string, it cannot be altered in place in memory, but instead, any alteration causes a copy with the modification to be created in a new memory location and the reference is updated to refer to the copy. For example if you append a character you actually get a whole new string in a whole new memory location.
StringBuilder is mutable. That means that you can change the object in place in memory without creating a copy. For example, if you append a character to StringBuilder it can add it to the same space in memory. There is one exception. If you add so many characters that the next character you add make the string bigger than the memory location’s capacity, a new object in memory is created. The default capacity for this implementation is 16 characters, and the default maximum capacity is Int32.MaxValue. (1) So if you are playing with Strings greater than 16 characters, you should instantiate your StringBuilder objects with a capacity greater than 16.
- So can you modify a string in place in memory?
Answer:
No. And yes.
Read this MSDN article: How to: Modify String Contents (C# Programming Guide)
The short version of the above article is that you can’t normally. But if you use “unsafe” you can, the above link provides the following unsafe code:
class UnsafeString { unsafe static void Main(string[] args) { // Compiler will store (intern) // these strings in same location. string s1 = "Hello"; string s2 = "Hello"; // Change one string using unsafe code. fixed (char* p = s1) { p[0] = 'C'; } // Both strings have changed. Console.WriteLine(s1); Console.WriteLine(s2); // Keep console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
Would you actually do this though?
- What is the easiest way to reverse a String?
public static string ReverseString(string s) { char[] arr = s.ToCharArray(); Array.Reverse(arr); return new string(arr); }
Notice another C# class called Array was used. This class, like String and StringBuilder, is another class that you may be forgetting about an overlooking.
When I was asked how to reverse a string, the Array.Reverse function did not come to mind and so I recreated it with this function, which obviously takes more time and a developer’s time is too expensive to recreate work already done for you.
private static String Reverse(String inString) { char[] myChars = inString.ToCharArray(); for (int i = 0, j = inString.Length - 1; i < inString.Length / 2; i++, j--) { char tmp = inString[j]; myChars[j] = inString[i]; myChars[i] = tmp; } return new string(myChars); }
- Which is more efficient for concatenation or appending characters, String or StringBuilder?
StringBuilder is by far more efficient. Microsoft has a knowledge base article on this here: http://support.microsoft.com/kb/306822
I changed the code slightly to use the Stopwatch to time the ticks to get more accurate data.
using System; using System.Diagnostics; namespace StringBuilderPerformance { class Program { static void Main(string[] args) { // The greater the loops the better the performance ratio const int sLen = 30, Loops = 10000; int i; string sSource = new String('X', sLen); string sDest = ""; // // Time string concatenation. // Stopwatch timer = new Stopwatch(); timer.Start(); for (i = 0; i < Loops; i++) sDest += sSource; timer.Stop(); long stringTicks = timer.ElapsedTicks; Console.WriteLine("Concatenation took " + stringTicks + " ticks."); // // Time StringBuilder. // timer.Reset(); timer.Start(); System.Text.StringBuilder sb = new System.Text.StringBuilder((int)(sLen * Loops * 1.1)); for (i = 0; i < Loops; i++) sb.Append(sSource); sDest = sb.ToString(); timer.Stop(); long stringBuilderTicks = timer.ElapsedTicks; Console.WriteLine("String Builder took " + stringBuilderTicks + " ticks."); Console.WriteLine("Using StringBuilder takes " + ((double)stringBuilderTicks / (double)stringTicks * (double)100).ToString("F") + "% of the time that using String takes."); // // Make the console window stay open // so that you can see the results when running from the IDE. // Console.WriteLine(); Console.Write("Press any key to finish ... "); Console.ReadKey(); } } }
Here are the results:
Concatenation took 5338055 ticks.
String Builder took 2213 ticks.
Using StringBuilder takes 0.04% of the time that using String takes.Press any key to finish …
Anyway, I hope this post helps you remember the basics.
I think the original code’s use of a map is odd and cinfusong. At first blush it looks as If what is being represented is an ordered collection that is indexed by an integer there are twenty in you example and therefore you should use an ordered collection. I think this post should be about changing the tortured use of a map rather then optimizing it. Even if the map is required, the relationship between labels, things and others should be made more explicit.